﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Jacaranda.Rules;
using System.Xml.Serialization;
using System.IO;

namespace Jacaranda.Model
{
    public class FieldMetadata
    {
        public string PropertyName { get; set; }

        public Type PropertyType { get; set; }

        public bool Ignore { get; set; }

        public string ExpressionName { get; set; }

        public string FieldName { get; set; }

        public bool IsPrimaryKey { get; set; }

        public bool IsDbPrimaryKey { get; set; }

        public bool IsAutoIncrease { get; set; }

        public bool IsIndex { get; set; }

        public bool IsGuid { get; set; }

        public bool IsXml { get; set; }

        public string DisplayName { get; set; }

        public ValidationRule[] ValidationRules { get; set; }

        public int? Length { get; set; }

        public object DefaultValue { get; set; }


        internal string GetDatabaseType()
        {
            if (CompareType(PropertyType, typeof(string)))
            {
                if (IsGuid)
                {
                    return "CHAR(36)";
                }
                else
                {
                    return string.Format("NVARCHAR({0})", (Length == null) ? "MAX" : Length.ToString());
                }
            }
            else if (CompareType(PropertyType, typeof(int)))
            {
                return "INT";
            }
            else if (CompareType(PropertyType, typeof(decimal)))
            {
                return "DECIMAL (18,3)";
            }
            else if (CompareType(PropertyType, typeof(DateTime)))
            {
                return "DATETIME";
            }
            else if (CompareType(PropertyType, typeof(bool)))
            {
                return "INT";
            }
            else if (CompareType(PropertyType, typeof(double)))
            {
                return "FLOAT";
            }
            else if (CompareType(PropertyType, typeof(long)))
            {
                return "BIGINT";
            }
            else if (CompareType(PropertyType, typeof(byte)))
            {
                return "TINYINT";
            }
            else if (CompareType(PropertyType, typeof(short)))
            {
                return "SMALLINT";
            }
            else if (CompareType(PropertyType, typeof(Enum)))
            {
                Type enumType = GetActualType(PropertyType);
                return string.Format("VARCHAR({0})", Enum.GetNames(enumType).Max(i => i.Length + 1));
            }
            else
            {
                return "NVARCHAR(MAX)";
            }
        }

        private bool CompareType(Type type1, Type type2)
        {
            if (type2.IsAssignableFrom(type1))
            {
                return true;
            }
            else if (type1.Name.StartsWith("Nullable"))
            {
                return type1.GetGenericArguments().Length == 1 && type2.IsAssignableFrom(type1.GetGenericArguments()[0]);
            }
            else
            {
                return false;
            }
        }

        private Type GetActualType(Type type)
        {
            if (type.Name.StartsWith("Nullable"))
            {
                return type.GetGenericArguments()[0];
            }
            else
            {
                return type;
            }
        }


        public void SetValue(Entity entity, object value)
        {
            entity.GetType().GetProperty(PropertyName).SetValue(entity, NormalizeValue(value), null);
        }

        private object NormalizeValue(object value)
        {
            if (value == null)
            {
                return null;
            }

            if (CompareType(PropertyType, typeof(string)))
            {
                return Convert.ToString(value);
            }
            else if (CompareType(PropertyType, typeof(bool)))
            {
                return Convert.ToBoolean(value);
            }
            else if (CompareType(PropertyType, typeof(decimal)))
            {
                return Convert.ToDecimal(value);
            }
            else if (CompareType(PropertyType, typeof(int)))
            {
                return Convert.ToInt32(value);
            }
            else if (CompareType(PropertyType, typeof(long)))
            {
                return Convert.ToInt64(value);
            }
            else if (CompareType(PropertyType, typeof(short)))
            {
                return Convert.ToInt16(value);
            }
            else if (CompareType(PropertyType, typeof(byte)))
            {
                return Convert.ToByte(value);
            }
            else if (CompareType(PropertyType, typeof(Enum)))
            {
                Type enumType = GetActualType(PropertyType);
                return Enum.Parse(enumType, value.ToString());
            }
            else if (IsXml)
            {
                if (string.IsNullOrEmpty((string)value))
                {
                    return null;
                }

                XmlSerializer s = new XmlSerializer(PropertyType);
                using (StringReader reader = new StringReader((string)value))
                {
                    return s.Deserialize(reader);
                }
            }
            else
            {
                return value;
            }
        }

        public object GetValue(Entity entity)
        {
            object value = entity.GetType().GetProperty(PropertyName).GetValue(entity, null);

            if (!IsXml || value == null)
            {
                return value;
            }
            else
            {
                XmlSerializer s = new XmlSerializer(value.GetType());
                using (StringWriter writer = new StringWriter())
                {
                    s.Serialize(writer, value);
                    return writer.ToString();
                }
            }
        }

        internal void ApplyDefaultValue(Entity entity)
        {
            if (DefaultValue != null)
            {
                SetValue(entity, DefaultValue);
            }
        }
    }
}
